/**
 * @class UI-功能类-太阳光耀
 * @author DuskyHuang 圣子
 * @description 自动追随太阳（或任意你想要的光源）的位置，并在屏幕上绘制太阳中心光耀、周围光晕、散射光斑。
 * @description 您可以设置光耀的遮挡参数和发光参数，drawcall不超过1。
 */

import { _decorator, Component, Node, Camera, Vec3, screen, view, Widget, Material, Sprite, renderer, Vec4, PhysicsSystem, geometry, Color, gfx, Vec2, v3, UIOpacity } from 'cc';
import { core, Notifications, utils } from 'cfw';
import { SceneGraph } from 'const';
import { cosmos, CosmosProxy, solar, visit } from 'proxies';
import { SolarSystem } from 'prefabs';
import { attenuate } from 'algorithms';
import { View } from 'notifications';
const { ccclass, requireComponent } = _decorator;
const { group } = utils;

const useOutScreen: Validator = function (this: SunFlare) { return this.autoHideOutScreen; }
const useOcclusive: Validator = function (this: SunFlare) { return this.autoHideOcclusive; }
const useSpotColor: Validator = function (this: SunFlare) { return this.useCustomSpotColor; }

@ccclass('cocosmos.sun-flare')
@requireComponent(Sprite)
@requireComponent(Widget)
export class SunFlare extends Component {

    @group("主参数.A", "主相机（3D相机）", Camera) public mainCamera: Camera = null;
    @group("主参数.A", "太阳节点（发光源）", Node) public sun: Node = null;
    @group("遮挡设置.B", "出屏隐藏") public autoHideOutScreen: boolean = true;
    @group("遮挡设置.B", "出屏容忍度", ['unit', 'pixel'], useOutScreen) public tolerance: number = 50;
    @group("遮挡设置.B", "遮挡隐藏") public autoHideOcclusive: boolean = true;
    @group("遮挡设置.B", "触发器遮挡", useOcclusive) public triggerOcclude: boolean = false;
    @group("发光设置.C", "中心光耀强度", [0, 2, 0.01]) public flareStrength: number = 1;
    @group("发光设置.C", "周围光晕强度", [0, 2, 0.01]) public haloStrength: number = 1;
    @group("发光设置.C", "散射光斑强度", [0, 2, 0.01]) public spotsStrength: number = 1;
    @group("发光设置.C", "中心光耀大小", [0]) public flareSize: number = 320;
    @group("发光设置.C", "周围光晕大小", [0]) public haloSize: number = 400;
    @group("发光设置.C", "光晕旋转角度", [0, 360, 0.1]) public haloAngle: num.deg = 0;
    @group("发光设置.C", "散射光斑大小", [0, 2, 0.01]) public spotsScale: number = 1;
    @group("发光设置.C", "指定光斑渐变色") public useCustomSpotColor: boolean = false;
    @group("发光设置.C", "光斑颜色开始", useSpotColor) public spotColorFrom: Color = new Color(255, 255, 0, 255);
    @group("发光设置.C", "光斑颜色结束", useSpotColor) public spotColorTo: Color = new Color(255, 125, 0, 127);

    protected onLoad(): void { this._init()._listen(); }
    protected onDestroy(): void { core.unload(this); }

    private _init(): this {
        this.mainCamera = this.mainCamera ?? utils.findCamera();
        this._material = this.getComponent(Sprite).material;
        this._pass0 = this._material.passes[0];
        this._handleArgs0 = this._pass0.getHandle('args0');
        this._handleArgs1zw = this._pass0.getHandle('args1', 2, gfx.Type.FLOAT2);
        this._handleArgs2 = this._pass0.getHandle('args2');
        this._handleHAngle = this._pass0.getHandle('hAngle');
        this._handleColorA = this._pass0.getHandle('spotColorA');
        this._handleColorB = this._pass0.getHandle('spotColorB');
        this._star = this.node.getChildByName('Star')!.getComponent(UIOpacity);
        return this;
    }

    private _listen(): void {
        updateSize();
        screen.on('window-resize', () => {
            this.getComponent(Widget).updateAlignment();
            updateSize();
        });
        core.on(Notifications.Time.POST_TICK, this.onTick, this);
        core.on(View.Transform.FACE_OVER, () => this._consume = true);
    }

    protected start(): void {
        const solarSys = utils.findComp(SolarSystem, COSMOS, SOLAR);
        this.sun = solarSys.sun?.node;
    }

    public get invalid(): boolean { return !this.node.parent || !this.mainCamera || !this.sun }

    private _attenuate(dt: time.sec): void {
        this.sun.inverseTransformPoint(v3a, this.mainCamera.node.worldPosition);
        const dist = v3a.length();
        const att = attenuate(dist, [3, 20, 2000, 10000], false);
        const light = attenuate(dist, [0, 1, 500, 1000], false);
        this._starlight = Math.lerp(this._starlight, visit.inSolar ? 0 : light, dt);
        this._strength = Math.lerp(this._strength, att, dt);
    }

    private _assign(x: num.px, y: num.px): void {
        this.flareStrength = this.haloStrength = this.spotsStrength = this._strength;
        this.flareSize = 320 * this._strength + 32;
        this.haloSize = 400 * this._strength + 40;
        this.haloAngle = this.mainCamera.node.eulerAngles.y;
        this._star.node.setPosition(x, y);
        this._star.opacity = 191 * this._starlight;
        // this._star.node.setScale(5 * this._starlight, 5 * this._starlight);
    }

    private _consume = false;
    protected onTick(dt?: time.sec, tick?: num.int): any {
        if (this.invalid) return this.node.active = this._consume = false;
        if (!cosmos.solar || !solar.showSun) return this.node.active = this._consume = false;
        const { worldPosition: sp } = this.sun, { forward, worldPosition: mp } = this.mainCamera.node;
        if (Vec3.dot(forward, Vec3.subtract(v3a, sp, mp)) < 0) return this.node.active = this._consume = false;
        this.mainCamera.convertToUINode(sp, this.node.parent, v3a);
        const { x, y } = this._consume ? store : v3a;   // For Hack
        store.set(v3a);
        this._consume = false;
        if (this.autoHideOutScreen) {
            const out = Math.abs(x) > Hor + this.tolerance || Math.abs(y) > Ver + this.tolerance;
            if (out) return this.node.active = false;
        }
        v4a.z = x; v4a.w = y;
        this._pass0.setUniform(this._handleArgs0, v4a);
        const occ = this.autoHideOcclusive && this._occlusiveCheck();
        if (occ) {
            // this._starlight = Math.lerp(this._starlight, 0, dt * 4);
            this._strength = Math.lerp(this._strength, 0, dt * 2);
        } else {
            this._attenuate(dt);
        }
        this._assign(x, y);
        v2a.set(this.flareSize, this.haloSize);
        this._pass0.setUniform(this._handleArgs1zw, v2a);
        this._pass0.setUniform(this._handleHAngle, this.haloAngle);
        v4b.set(this.flareStrength, this.haloStrength, this.spotsStrength, this.spotsScale);
        this._pass0.setUniform(this._handleArgs2, v4b);
        if (this.useCustomSpotColor) {
            this._pass0.setUniform(this._handleColorA, this.spotColorFrom);
            this._pass0.setUniform(this._handleColorB, this.spotColorTo);
        }
        this.node.active = true;
    }

    private _occlusiveCheck(): boolean {
        const outRay = new geometry.Ray();
        geometry.Ray.fromPoints(outRay, this.mainCamera.node.worldPosition, this.sun.worldPosition);
        const mask = 0xffffffff;
        const maxDistance = 100;
        const queryTrigger = this.triggerOcclude;
        if (PhysicsSystem.instance.raycast(outRay, mask, maxDistance, queryTrigger)) {
            return notEmptyArr(PhysicsSystem.instance.raycastResults);
        } else return false;
    }

    private _material: Material | renderer.MaterialInstance;
    private _pass0: renderer.Pass;
    private _handleArgs0: handle = 0;
    private _handleArgs1zw: handle = 0;
    private _handleArgs2: handle = 0;
    private _handleHAngle: handle = 0;
    private _handleColorA: handle = 0;
    private _handleColorB: handle = 0;
    private _strength: num.pct = 0;
    private _starlight: num.pct = 0;
    private _star: UIOpacity;
}

const v2a = new Vec2;
const v3a = new Vec3;
const store = new Vec3; // For Hack
const v4a = new Vec4;
const v4b = new Vec4;
let Hor = 0, Ver = 0;
function updateSize(): void {
    const { width, height } = view.getVisibleSize();
    Hor = width / 2;
    Ver = height / 2;
    v4a.x = width;
    v4a.y = height;
}
const ATT_SOLAR = [-7, -6, -3.4, -1.4] as attenuation;
const ATT_PLANET = [-99, -98, -3.4, -1.4] as attenuation;
const SPN_SOLAR = 5.6;

const { COSMOS, SOLAR } = SceneGraph;
// const { Scopes: { Solar } } = CosmosProxy;